﻿using System;

namespace ch06.binary.interpreter
{
    public static partial class Instr
    {
        // const
        internal static void I32Const(ref Vm vm, object args)
        {
            vm.OperandStack.pushS32((int) args);
        }

        internal static void I64Const(ref Vm vm, object args)
        {
            vm.OperandStack.PushS64((long) args);
        }

        internal static void F32Const(ref Vm vm, object args)
        {
            vm.OperandStack.pushF32((float) args);
        }

        internal static void F64Const(ref Vm vm, object args)
        {
            vm.OperandStack.pushF64((double) args);
        }

        // i32 test & rel
        private static void I32Eqz(ref Vm vm, object args)
        {
            vm.OperandStack.pushBool(vm.OperandStack.popU32() == 0);
        }

        private static void I32Eq(ref Vm vm, object args)
        {
            var (v2, v1) = (vm.OperandStack.popU32(), vm.OperandStack.popU32());
            vm.OperandStack.pushBool(v1 == v2);
        }

        private static void I32Ne(ref Vm vm, object args)
        {
            var (v2, v1) = (vm.OperandStack.popU32(), vm.OperandStack.popU32());
            vm.OperandStack.pushBool(v1 != v2);
        }

        private static void I32LtS(ref Vm vm, object args)
        {
            var (v2, v1) = (vm.OperandStack.popS32(), vm.OperandStack.popS32());
            vm.OperandStack.pushBool(v1 < v2);
        }

        private static void I32LtU(ref Vm vm, object args)
        {
            var (v2, v1) = (vm.OperandStack.popU32(), vm.OperandStack.popU32());
            vm.OperandStack.pushBool(v1 < v2);
        }

        private static void I32GtS(ref Vm vm, object args)
        {
            var (v2, v1) = (vm.OperandStack.popS32(), vm.OperandStack.popS32());
            vm.OperandStack.pushBool(v1 > v2);
        }

        private static void I32GtU(ref Vm vm, object args)
        {
            var (v2, v1) = (vm.OperandStack.popU32(), vm.OperandStack.popU32());
            vm.OperandStack.pushBool(v1 > v2);
        }

        private static void I32LeS(ref Vm vm, object args)
        {
            var (v2, v1) = (vm.OperandStack.popS32(), vm.OperandStack.popS32());
            vm.OperandStack.pushBool(v1 <= v2);
        }

        private static void I32LeU(ref Vm vm, object args)
        {
            var (v2, v1) = (vm.OperandStack.popU32(), vm.OperandStack.popU32());
            vm.OperandStack.pushBool(v1 <= v2);
        }

        private static void I32GeS(ref Vm vm, object args)
        {
            var (v2, v1) = (vm.OperandStack.popS32(), vm.OperandStack.popS32());
            vm.OperandStack.pushBool(v1 >= v2);
        }

        private static void I32GeU(ref Vm vm, object args)
        {
            var (v2, v1) = (vm.OperandStack.popU32(), vm.OperandStack.popU32());
            vm.OperandStack.pushBool(v1 >= v2);
        }

        // i64 test & rel
        private static void I64Eqz(ref Vm vm, object args)
        {
            vm.OperandStack.pushBool(vm.OperandStack.PopU64() == 0);
        }

        private static void I64Eq(ref Vm vm, object args)
        {
            var (v2, v1) = (vm.OperandStack.PopU64(), vm.OperandStack.PopU64());
            vm.OperandStack.pushBool(v1 == v2);
        }

        private static void I64Ne(ref Vm vm, object args)
        {
            var (v2, v1) = (vm.OperandStack.PopU64(), vm.OperandStack.PopU64());
            vm.OperandStack.pushBool(v1 != v2);
        }

        private static void I64LtS(ref Vm vm, object args)
        {
            var (v2, v1) = (vm.OperandStack.PopS64(), vm.OperandStack.PopS64());
            vm.OperandStack.pushBool(v1 < v2);
        }

        private static void I64LtU(ref Vm vm, object args)
        {
            var (v2, v1) = (vm.OperandStack.PopU64(), vm.OperandStack.PopU64());
            vm.OperandStack.pushBool(v1 < v2);
        }

        private static void I64GtS(ref Vm vm, object args)
        {
            var (v2, v1) = (vm.OperandStack.PopS64(), vm.OperandStack.PopS64());
            vm.OperandStack.pushBool(v1 > v2);
        }

        private static void I64GtU(ref Vm vm, object args)
        {
            var (v2, v1) = (vm.OperandStack.PopU64(), vm.OperandStack.PopU64());
            vm.OperandStack.pushBool(v1 > v2);
        }

        private static void I64LeS(ref Vm vm, object args)
        {
            var (v2, v1) = (vm.OperandStack.PopS64(), vm.OperandStack.PopS64());
            vm.OperandStack.pushBool(v1 <= v2);
        }

        private static void I64LeU(ref Vm vm, object args)
        {
            var (v2, v1) = (vm.OperandStack.PopU64(), vm.OperandStack.PopU64());
            vm.OperandStack.pushBool(v1 <= v2);
        }

        private static void I64GeS(ref Vm vm, object args)
        {
            var (v2, v1) = (vm.OperandStack.PopS64(), vm.OperandStack.PopS64());
            vm.OperandStack.pushBool(v1 >= v2);
        }

        private static void I64GeU(ref Vm vm, object args)
        {
            var (v2, v1) = (vm.OperandStack.PopU64(), vm.OperandStack.PopU64());
            vm.OperandStack.pushBool(v1 >= v2);
        }

        // f32 rel
        private static void F32Eq(ref Vm vm, object args)
        {
            var (v2, v1) = (vm.OperandStack.popF32(), vm.OperandStack.popF32());
            vm.OperandStack.pushBool(v1.Equals(v2));
        }

        private static void F32Ne(ref Vm vm, object args)
        {
            var (v2, v1) = (vm.OperandStack.popF32(), vm.OperandStack.popF32());
            vm.OperandStack.pushBool(!v1.Equals(v2));
        }

        private static void F32Lt(ref Vm vm, object args)
        {
            var (v2, v1) = (vm.OperandStack.popF32(), vm.OperandStack.popF32());
            vm.OperandStack.pushBool(v1 < v2);
        }

        private static void F32Gt(ref Vm vm, object args)
        {
            var (v2, v1) = (vm.OperandStack.popF32(), vm.OperandStack.popF32());
            vm.OperandStack.pushBool(v1 > v2);
        }

        private static void F32Le(ref Vm vm, object args)
        {
            var (v2, v1) = (vm.OperandStack.popF32(), vm.OperandStack.popF32());
            vm.OperandStack.pushBool(v1 <= v2);
        }

        private static void F32Ge(ref Vm vm, object args)
        {
            var (v2, v1) = (vm.OperandStack.popF32(), vm.OperandStack.popF32());
            vm.OperandStack.pushBool(v1 >= v2);
        }

        // f64 rel
        private static void F64Eq(ref Vm vm, object args)
        {
            var (v2, v1) = (vm.OperandStack.popF64(), vm.OperandStack.popF64());
            vm.OperandStack.pushBool(v1.Equals(v2));
        }

        private static void F64Ne(ref Vm vm, object args)
        {
            var (v2, v1) = (vm.OperandStack.popF64(), vm.OperandStack.popF64());
            vm.OperandStack.pushBool(!v1.Equals(v2));
        }

        private static void F64Lt(ref Vm vm, object args)
        {
            var (v2, v1) = (vm.OperandStack.popF64(), vm.OperandStack.popF64());
            vm.OperandStack.pushBool(v1 < v2);
        }

        private static void F64Gt(ref Vm vm, object args)
        {
            var (v2, v1) = (vm.OperandStack.popF64(), vm.OperandStack.popF64());
            vm.OperandStack.pushBool(v1 > v2);
        }

        private static void F64Le(ref Vm vm, object args)
        {
            var (v2, v1) = (vm.OperandStack.popF64(), vm.OperandStack.popF64());
            vm.OperandStack.pushBool(v1 <= v2);
        }

        private static void F64Ge(ref Vm vm, object args)
        {
            var (v2, v1) = (vm.OperandStack.popF64(), vm.OperandStack.popF64());
            vm.OperandStack.pushBool(v1 >= v2);
        }

// i32 arithmetic & bitwise
        private static void I32Clz(ref Vm vm, object args)
        {
            vm.OperandStack.pushU32((uint) (Bits.LeadingZeros32(vm.OperandStack.popU32())));
        }

        private static void I32Ctz(ref Vm vm, object args)
        {
            vm.OperandStack.pushU32((uint) (Bits.TrailingZeros32(vm.OperandStack.popU32())));
        }

        private static void I32PopCnt(ref Vm vm, object args)
        {
            vm.OperandStack.pushU32((uint) (Bits.OnesCount32(vm.OperandStack.popU32())));
        }

        private static void I32Add(ref Vm vm, object args)
        {
            var (v2, v1) = (vm.OperandStack.popU32(), vm.OperandStack.popU32());
            vm.OperandStack.pushU32(v1 + v2);
        }

        private static void I32Sub(ref Vm vm, object args)
        {
            var (v2, v1) = (vm.OperandStack.popU32(), vm.OperandStack.popU32());
            vm.OperandStack.pushU32(v1 - v2);
        }

        private static void I32Mul(ref Vm vm, object args)
        {
            var (v2, v1) = (vm.OperandStack.popU32(), vm.OperandStack.popU32());
            vm.OperandStack.pushU32(v1 * v2);
        }

        private static void I32DivS(ref Vm vm, object args)
        {
            var (v2, v1) = (vm.OperandStack.popS32(), vm.OperandStack.popS32());
            if (v1 == int.MinValue && v2 == -1)
            {
                Panic.Run(Errors.ErrIntOverflow);
            }

            vm.OperandStack.pushS32(v1 / v2);
        }

        private static void I32DivU(ref Vm vm, object args)
        {
            var (v2, v1) = (vm.OperandStack.popU32(), vm.OperandStack.popU32());
            vm.OperandStack.pushU32(v1 / v2);
        }

        private static void I32RemS(ref Vm vm, object args)
        {
            var (v2, v1) = (vm.OperandStack.popS32(), vm.OperandStack.popS32());
            vm.OperandStack.pushS32(v1 % v2);
        }

        private static void I32RemU(ref Vm vm, object args)
        {
            var (v2, v1) = (vm.OperandStack.popU32(), vm.OperandStack.popU32());
            vm.OperandStack.pushU32(v1 % v2);
        }

        private static void I32And(ref Vm vm, object args)
        {
            var (v2, v1) = (vm.OperandStack.popU32(), vm.OperandStack.popU32());
            vm.OperandStack.pushU32(v1 & v2);
        }

        private static void I32Or(ref Vm vm, object args)
        {
            var (v2, v1) = (vm.OperandStack.popU32(), vm.OperandStack.popU32());
            vm.OperandStack.pushU32(v1 | v2);
        }

        private static void I32Xor(ref Vm vm, object args)
        {
            var (v2, v1) = (vm.OperandStack.popU32(), vm.OperandStack.popU32());
            vm.OperandStack.pushU32(v1 ^ v2);
        }

        private static void I32Shl(ref Vm vm, object args)
        {
            var (v2, v1) = (vm.OperandStack.popU32(), vm.OperandStack.popU32());
            vm.OperandStack.pushU32(v1 << (int) (v2 % 32));
        }

        private static void I32ShrS(ref Vm vm, object args)
        {
            var (v2, v1) = (vm.OperandStack.popU32(), vm.OperandStack.popS32());
            vm.OperandStack.pushS32(v1 >> (int) (v2 % 32));
        }

        private static void I32ShrU(ref Vm vm, object args)
        {
            var (v2, v1) = (vm.OperandStack.popU32(), vm.OperandStack.popU32());
            vm.OperandStack.pushU32(v1 >> (int) (v2 % 32));
        }

        private static void I32Rotl(ref Vm vm, object args)
        {
            var (v2, v1) = (vm.OperandStack.popU32(), vm.OperandStack.popU32());
            vm.OperandStack.pushU32(Bits.RotateLeft32(v1, (int) (v2)));
        }

        private static void I32Rotr(ref Vm vm, object args)
        {
            var (v2, v1) = (vm.OperandStack.popU32(), vm.OperandStack.popU32());
            vm.OperandStack.pushU32(Bits.RotateLeft32(v1, -(int) (v2)));
        }

// i64 arithmetic & bitwise
        private static void I64Clz(ref Vm vm, object args)
        {
            vm.OperandStack.PushU64((ulong) (Bits.LeadingZeros64(vm.OperandStack.PopU64())));
        }

        private static void I64Ctz(ref Vm vm, object args)
        {
            vm.OperandStack.PushU64((ulong) (Bits.TrailingZeros64(vm.OperandStack.PopU64())));
        }

        private static void I64PopCnt(ref Vm vm, object args)
        {
            vm.OperandStack.PushU64((ulong) Bits.OnesCount64(vm.OperandStack.PopU64()));
        }

        private static void I64Add(ref Vm vm, object args)
        {
            var (v2, v1) = (vm.OperandStack.PopU64(), vm.OperandStack.PopU64());
            vm.OperandStack.PushU64(v1 + v2);
        }

        private static void I64Sub(ref Vm vm, object args)
        {
            var (v2, v1) = (vm.OperandStack.PopU64(), vm.OperandStack.PopU64());
            vm.OperandStack.PushU64(v1 - v2);
        }

        private static void I64Mul(ref Vm vm, object args)
        {
            var (v2, v1) = (vm.OperandStack.PopU64(), vm.OperandStack.PopU64());
            vm.OperandStack.PushU64(v1 * v2);
        }

        private static void I64DivS(ref Vm vm, object args)
        {
            var (v2, v1) = (vm.OperandStack.PopS64(), vm.OperandStack.PopS64());
            if (v1 == long.MinValue && v2 == -1)
            {
                Panic.Run(Errors.ErrIntOverflow);
            }

            vm.OperandStack.PushS64(v1 / v2);
        }

        private static void I64DivU(ref Vm vm, object args)
        {
            var (v2, v1) = (vm.OperandStack.PopU64(), vm.OperandStack.PopU64());
            vm.OperandStack.PushU64(v1 / v2);
        }

        private static void I64RemS(ref Vm vm, object args)
        {
            var (v2, v1) = (vm.OperandStack.PopS64(), vm.OperandStack.PopS64());
            vm.OperandStack.PushS64(v1 % v2);
        }

        private static void I64RemU(ref Vm vm, object args)
        {
            var (v2, v1) = (vm.OperandStack.PopU64(), vm.OperandStack.PopU64());
            vm.OperandStack.PushU64(v1 % v2);
        }

        private static void I64And(ref Vm vm, object args)
        {
            var (v2, v1) = (vm.OperandStack.PopU64(), vm.OperandStack.PopU64());
            vm.OperandStack.PushU64(v1 & v2);
        }

        private static void I64Or(ref Vm vm, object args)
        {
            var (v2, v1) = (vm.OperandStack.PopU64(), vm.OperandStack.PopU64());
            vm.OperandStack.PushU64(v1 | v2);
        }

        private static void I64Xor(ref Vm vm, object args)
        {
            var (v2, v1) = (vm.OperandStack.PopU64(), vm.OperandStack.PopU64());
            vm.OperandStack.PushU64(v1 ^ v2);
        }

        private static void I64Shl(ref Vm vm, object args)
        {
            var (v2, v1) = (vm.OperandStack.PopU64(), vm.OperandStack.PopU64());
            vm.OperandStack.PushU64(v1 << (int) (v2 % 64));
        }

        private static void I64ShrS(ref Vm vm, object args)
        {
            var (v2, v1) = (vm.OperandStack.PopU64(), vm.OperandStack.PopS64());
            vm.OperandStack.PushS64(v1 >> (int) (v2 % 64));
        }

        private static void I64ShrU(ref Vm vm, object args)
        {
            var (v2, v1) = (vm.OperandStack.PopU64(), vm.OperandStack.PopU64());
            vm.OperandStack.PushU64(v1 >> (int) (v2 % 64));
        }

        private static void I64Rotl(ref Vm vm, object args)
        {
            var (v2, v1) = (vm.OperandStack.PopU64(), vm.OperandStack.PopU64());
            vm.OperandStack.PushU64(Bits.RotateLeft64(v1, (int) (v2)));
        }

        private static void I64Rotr(ref Vm vm, object args)
        {
            var (v2, v1) = (vm.OperandStack.PopU64(), vm.OperandStack.PopU64());
            vm.OperandStack.PushU64(Bits.RotateLeft64(v1, -(int) (v2)));
        }

        // f32 arithmetic
        private static void F32Abs(ref Vm vm, object args)
        {
            vm.OperandStack.pushF32(System.Math.Abs(vm.OperandStack.popF32()));
        }

        private static void F32Neg(ref Vm vm, object args)
        {
            vm.OperandStack.pushF32(-vm.OperandStack.popF32());
        }

        private static void F32Ceil(ref Vm vm, object args)
        {
            vm.OperandStack.pushF32((float) (System.Math.Ceiling(vm.OperandStack.popF32())));
        }

        private static void F32Floor(ref Vm vm, object args)
        {
            vm.OperandStack.pushF32((float) (System.Math.Floor(vm.OperandStack.popF32())));
        }

        private static void F32Trunc(ref Vm vm, object args)
        {
            vm.OperandStack.pushF32((float) (System.Math.Truncate(vm.OperandStack.popF32())));
        }

        private static void F32Nearest(ref Vm vm, object args)
        {
            vm.OperandStack.pushF32((float) System.Math.Round(vm.OperandStack.popF32(), MidpointRounding.ToEven));
        }

        private static void F32Sqrt(ref Vm vm, object args)
        {
            vm.OperandStack.pushF32((float) System.Math.Sqrt(vm.OperandStack.popF32()));
        }

        private static void F32Add(ref Vm vm, object args)
        {
            var (v2, v1) = (vm.OperandStack.popF32(), vm.OperandStack.popF32());
            vm.OperandStack.pushF32(v1 + v2);
        }

        private static void F32Sub(ref Vm vm, object args)
        {
            var (v2, v1) = (vm.OperandStack.popF32(), vm.OperandStack.popF32());
            vm.OperandStack.pushF32(v1 - v2);
        }

        private static void F32Mul(ref Vm vm, object args)
        {
            var (v2, v1) = (vm.OperandStack.popF32(), vm.OperandStack.popF32());
            vm.OperandStack.pushF32(v1 * v2);
        }

        private static void F32Div(ref Vm vm, object args)
        {
            var (v2, v1) = (vm.OperandStack.popF32(), vm.OperandStack.popF32());
            vm.OperandStack.pushF32(v1 / v2);
        }

        private static void F32Min(ref Vm vm, object args)
        {
            var (v2, v1) = (vm.OperandStack.popF32(), vm.OperandStack.popF32());
            var v1NaN = float.IsNaN(v1);
            var v2NaN = float.IsNaN(v2);
            if (v1NaN && !v2NaN)
            {
                vm.OperandStack.pushF32(v1);
                return;
            }

            if (v2NaN && !v1NaN)
            {
                vm.OperandStack.pushF32(v2);
                return;
            }

            vm.OperandStack.pushF32(System.Math.Min(v1, v2));
        }

        private static void F32Max(ref Vm vm, object args)
        {
            var (v2, v1) = (vm.OperandStack.popF32(), vm.OperandStack.popF32());
            var v1NaN = float.IsNaN(v1);
            var v2NaN = float.IsNaN(v2);
            if (v1NaN && !v2NaN)
            {
                vm.OperandStack.pushF32(v1);
                return;
            }
            else if (v2NaN && !v1NaN)
            {
                vm.OperandStack.pushF32(v2);
                return;
            }

            vm.OperandStack.pushF32(System.Math.Max(v1, v2));
        }

        private static void F32CopySign(ref Vm vm, object args)
        {
            var (v2, v1) = (vm.OperandStack.popF32(), vm.OperandStack.popF32());
            vm.OperandStack.pushF32((float) (System.Math.CopySign(v1, v2)));
        }


        // f64 arithmetic
        private static void F64Abs(ref Vm vm, object args)
        {
            vm.OperandStack.pushF64(System.Math.Abs(vm.OperandStack.popF64()));
        }

        private static void F64Neg(ref Vm vm, object args)
        {
            vm.OperandStack.pushF64(-vm.OperandStack.popF64());
        }

        private static void F64Ceil(ref Vm vm, object args)
        {
            vm.OperandStack.pushF64(System.Math.Ceiling(vm.OperandStack.popF64()));
        }

        private static void F64Floor(ref Vm vm, object args)
        {
            vm.OperandStack.pushF64(System.Math.Floor(vm.OperandStack.popF64()));
        }

        private static void F64Trunc(ref Vm vm, object args)
        {
            vm.OperandStack.pushF64(System.Math.Truncate(vm.OperandStack.popF64()));
        }

        private static void F64Nearest(ref Vm vm, object args)
        {
            vm.OperandStack.pushF64(System.Math.Round(vm.OperandStack.popF64(), MidpointRounding.ToEven));
        }

        private static void F64Sqrt(ref Vm vm, object args)
        {
            vm.OperandStack.pushF64(System.Math.Sqrt(vm.OperandStack.popF64()));
        }

        private static void F64Add(ref Vm vm, object args)
        {
            var (v2, v1) = (vm.OperandStack.popF64(), vm.OperandStack.popF64());
            vm.OperandStack.pushF64(v1 + v2);
        }

        private static void F64Sub(ref Vm vm, object args)
        {
            var (v2, v1) = (vm.OperandStack.popF64(), vm.OperandStack.popF64());
            vm.OperandStack.pushF64(v1 - v2);
        }

        private static void F64Mul(ref Vm vm, object args)
        {
            var (v2, v1) = (vm.OperandStack.popF64(), vm.OperandStack.popF64());
            vm.OperandStack.pushF64(v1 * v2);
        }

        private static void F64Div(ref Vm vm, object args)
        {
            var (v2, v1) = (vm.OperandStack.popF64(), vm.OperandStack.popF64());
            vm.OperandStack.pushF64(v1 / v2);
        }

        private static void F64Min(ref Vm vm, object args)
        {
            var (v2, v1) = (vm.OperandStack.popF64(), vm.OperandStack.popF64());
            var v1NaN = double.IsNaN(v1);
            var v2NaN = double.IsNaN(v2);
            if (v1NaN && !v2NaN)
            {
                vm.OperandStack.pushF64(v1);
                return;
            }

            if (v2NaN && !v1NaN)
            {
                vm.OperandStack.pushF64(v2);
                return;
            }

            vm.OperandStack.pushF64(System.Math.Min(v1, v2));
        }

        private static void F64Max(ref Vm vm, object args)
        {
            var (v2, v1) = (vm.OperandStack.popF64(), vm.OperandStack.popF64());
            var v1NaN = double.IsNaN(v1);
            var v2NaN = double.IsNaN(v2);
            if (v1NaN && !v2NaN)
            {
                vm.OperandStack.pushF64(v1);
                return;
            }
            else if (v2NaN && !v1NaN)
            {
                vm.OperandStack.pushF64(v2);
                return;
            }

            vm.OperandStack.pushF64(System.Math.Max(v1, v2));
        }

        private static void F64CopySign(ref Vm vm, object args)
        {
            var (v2, v1) = (vm.OperandStack.popF64(), vm.OperandStack.popF64());
            vm.OperandStack.pushF64(System.Math.CopySign(v1, v2));
        }

// conversions
        private static void I32WrapI64(ref Vm vm, object args)
        {
            vm.OperandStack.pushU32((uint) (vm.OperandStack.PopU64()));
        }

        private static void I32TruncF32S(ref Vm vm, object args)
        {
            var f = System.Math.Truncate(vm.OperandStack.popF32());
            switch (f)
            {
                case > int.MaxValue or < int.MinValue:
                    Panic.Run(Errors.ErrIntOverflow);
                    break;
                case double.NaN:
                    Panic.Run(Errors.ErrConvertToInt);
                    break;
            }

            vm.OperandStack.pushS32((int) (f));
        }

        private static void I32TruncF32U(ref Vm vm, object args)
        {
            var f = System.Math.Truncate(vm.OperandStack.popF32());
            switch (f)
            {
                case > uint.MaxValue:
                case < 0:
                    Panic.Run(Errors.ErrIntOverflow);
                    break;
                case double.NaN:
                    Panic.Run(Errors.ErrConvertToInt);
                    break;
            }

            vm.OperandStack.pushU32((uint) f);
        }

        private static void I32TruncF64S(ref Vm vm, object args)
        {
            var f = System.Math.Truncate(vm.OperandStack.popF64());
            switch (f)
            {
                case > int.MaxValue:
                case < int.MinValue:
                    Panic.Run(Errors.ErrIntOverflow);
                    break;
                case double.NaN:
                    Panic.Run(Errors.ErrConvertToInt);
                    break;
            }

            vm.OperandStack.pushS32((int) f);
        }

        private static void I32TruncF64U(ref Vm vm, object args)
        {
            var f = System.Math.Truncate(vm.OperandStack.popF64());
            if (f > uint.MaxValue || f < 0)
            {
                Panic.Run(Errors.ErrIntOverflow);
            }

            if (double.IsNaN(f))
            {
                Panic.Run(Errors.ErrConvertToInt);
            }

            vm.OperandStack.pushU32((uint) f);
        }

        private static void I64ExtendI32S(ref Vm vm, object args)
        {
            vm.OperandStack.PushS64(vm.OperandStack.popS32());
        }

        private static void I64ExtendI32U(ref Vm vm, object args)
        {
            vm.OperandStack.PushU64(vm.OperandStack.popU32());
        }

        private static void I64TruncF32S(ref Vm vm, object args)
        {
            var f = System.Math.Truncate(vm.OperandStack.popF32());
            switch (f)
            {
                case >= long.MaxValue:
                case < long.MinValue:
                    Panic.Run(Errors.ErrIntOverflow);
                    break;
                case double.NaN:
                    Panic.Run(Errors.ErrConvertToInt);
                    break;
            }

            vm.OperandStack.PushS64((long) f);
        }

        private static void I64TruncF32U(ref Vm vm, object args)
        {
            var f = System.Math.Truncate(vm.OperandStack.popF32());
            if (f >= ulong.MaxValue || f < 0)
            {
                Panic.Run(Errors.ErrIntOverflow);
            }

            if (double.IsNaN(f))
            {
                Panic.Run(Errors.ErrConvertToInt);
            }

            vm.OperandStack.PushU64((ulong) f);
        }

        private static void I64TruncF64S(ref Vm vm, object args)
        {
            var f = System.Math.Truncate(vm.OperandStack.popF64());
            switch (f)
            {
                case >= long.MaxValue or < long.MinValue:
                    Panic.Run(Errors.ErrIntOverflow);
                    break;
                case double.NaN:
                    Panic.Run(Errors.ErrConvertToInt);
                    break;
            }

            vm.OperandStack.PushS64((long) (f));
        }

        private static void I64TruncF64U(ref Vm vm, object args)
        {
            var f = System.Math.Truncate(vm.OperandStack.popF64());
            switch (f)
            {
                case >= ulong.MaxValue or < 0:
                    Panic.Run(Errors.ErrIntOverflow);
                    break;
                case double.NaN:
                    Panic.Run(Errors.ErrConvertToInt);
                    break;
            }

            vm.OperandStack.PushU64((ulong) (f));
        }

        private static void F32ConvertI32S(ref Vm vm, object args)
        {
            vm.OperandStack.pushF32(vm.OperandStack.popS32());
        }

        private static void F32ConvertI32U(ref Vm vm, object args)
        {
            vm.OperandStack.pushF32(vm.OperandStack.popU32());
        }

        private static void F32ConvertI64S(ref Vm vm, object args)
        {
            vm.OperandStack.pushF32(vm.OperandStack.PopS64());
        }

        private static void F32ConvertI64U(ref Vm vm, object args)
        {
            vm.OperandStack.pushF32(vm.OperandStack.PopU64());
        }

        private static void F32DemoteF64(ref Vm vm, object args)
        {
            vm.OperandStack.pushF32((float) (vm.OperandStack.popF64()));
        }

        private static void F64ConvertI32S(ref Vm vm, object args)
        {
            vm.OperandStack.pushF64(vm.OperandStack.popS32());
        }

        private static void F64ConvertI32U(ref Vm vm, object args)
        {
            vm.OperandStack.pushF64(vm.OperandStack.popU32());
        }

        private static void F64ConvertI64S(ref Vm vm, object args)
        {
            vm.OperandStack.pushF64(vm.OperandStack.PopS64());
        }

        private static void F64ConvertI64U(ref Vm vm, object args)
        {
            vm.OperandStack.pushF64(vm.OperandStack.PopU64());
        }

        private static void F64PromoteF32(ref Vm vm, object args)
        {
            vm.OperandStack.pushF64(vm.OperandStack.popF32());
        }

        private static void I32ReinterpretF32(ref Vm vm, object args)
        {
            //vm.operandStack.pushU32(Math.Float32bits(vm.operandStack.popF32()))
        }

        private static void I64ReinterpretF64(ref Vm vm, object args)
        {
            //vm.operandStack.PushU64(Math.Float64bits(vm.operandStack.popF64()))
        }

        private static void F32ReinterpretI32(ref Vm vm, object args)
        {
            //vm.operandStack.pushF32(Math.Float32frombits(vm.operandStack.popU32()))
        }

        private static void F64ReinterpretI64(ref Vm vm, object args)
        {
            //vm.operandStack.pushF64(Math.Float64frombits(vm.operandStack.PopU64()))
        }

        private static void I32Extend8S(ref Vm vm, object args)
        {
            vm.OperandStack.pushS32((sbyte) (vm.OperandStack.popS32()));
        }

        private static void I32Extend16S(ref Vm vm, object args)
        {
            vm.OperandStack.pushS32((short) (vm.OperandStack.popS32()));
        }

        private static void I64Extend8S(ref Vm vm, object args)
        {
            vm.OperandStack.PushS64((sbyte) (vm.OperandStack.PopS64()));
        }

        private static void I64Extend16S(ref Vm vm, object args)
        {
            vm.OperandStack.PushS64((short) (vm.OperandStack.PopS64()));
        }

        private static void I64Extend32S(ref Vm vm, object args)
        {
            vm.OperandStack.PushS64((int) (vm.OperandStack.PopS64()));
        }

        private static void TruncSat(ref Vm vm, object args)
        {
            switch ((byte) args)
            {
                case 0: // i32.trunc_sat_f32_s
                    var v = TruncSatS(vm.OperandStack.popF32(), 32);
                    vm.OperandStack.pushS32((int) (v));
                    break;
                case 1: // i32.trunc_sat_f32_u
                    var v1 = TruncSatU(vm.OperandStack.popF32(), 32);
                    vm.OperandStack.pushU32((uint) (v1));
                    break;
                case 2: // i32.trunc_sat_f64_s
                    var v2 = TruncSatS(vm.OperandStack.popF64(), 32);
                    vm.OperandStack.pushS32((int) (v2));
                    break;
                case 3: // i32.trunc_sat_f64_u
                    var v3 = TruncSatU(vm.OperandStack.popF64(), 32);
                    vm.OperandStack.pushU32((uint) (v3));
                    break;
                case 4: // i64.trunc_sat_f32_s
                    var v4 = TruncSatS(vm.OperandStack.popF32(), 64);
                    vm.OperandStack.PushS64(v4);
                    break;
                case 5: // i64.trunc_sat_f32_u
                    var v5 = TruncSatU(vm.OperandStack.popF32(), 64);
                    vm.OperandStack.PushU64(v5);
                    break;
                case 6: // i64.trunc_sat_f64_s
                    var v6 = TruncSatS(vm.OperandStack.popF64(), 64);
                    vm.OperandStack.PushS64(v6);
                    break;
                case 7: // i64.trunc_sat_f64_u
                    var v7 = TruncSatU(vm.OperandStack.popF64(), 64);
                    vm.OperandStack.PushU64(v7);
                    break;
                default:
                    Panic.Run(Errors.ErrTrap);
                    break;
            }
        }

        private static ulong TruncSatU(double z, int n)
        {
            if (double.IsNaN(z))
            {
                return 0;
            }

            if (Math.IsInf(z, -1))
            {
                return 0;
            }

            var max = ((ulong) (1) << n) - 1;
            if (Math.IsInf(z, 1))
            {
                return max;
            }

            var x = System.Math.Truncate(z);
            if (x < 0)
            {
                return 0;
            }

            if (x >= max)
            {
                return max;
            }

            return (ulong) (x);
        }

        private static long TruncSatS(double z, int n)
        {
            if (double.IsNaN(z))
            {
                return 0;
            }

            var min = -((long) (1) << (n - 1));
            var max = ((long) (1) << (n - 1)) - 1;
            if (Math.IsInf(z, -1))
            {
                return min;
            }

            if (Math.IsInf(z, 1))
            {
                return max;
            }

            var x = System.Math.Truncate(z);
            if
                (x < min)
            {
                return min;
            }

            if (x >= max)
            {
                return max;
            }

            return (long) (x);
        }
    }
}